#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include "pa_mw.h"
#include "crc-ccitt.h"
#include "syscore.h"


#define get_mod_addr(msg)  (*(mod_addr_t *)((uint8_t *)(msg) + MW_CMD_OFFSET_MOD_ADDR))
#define get_cmd_code(msg)  (*((uint8_t *)(msg) + MW_CMD_OFFSET_CMD_CODE))
#define get_rsp_flag(msg)  (*((uint8_t *)(msg) + MW_CMD_OFFSET_RSP_FLAG))
#define get_cmd_size(msg)  (*((uint8_t *)(msg) + MW_CMD_OFFSET_CMD_SIZE))
#define get_crc_data(msg)  (*(uint16_t *)((uint8_t *)(msg) + MW_CMD_OFFSET_CMD_DATA + get_cmd_size(msg)))

#define set_mod_addr(msg, addr)  (*(mod_addr_t *)((uint8_t *)(msg) + MW_CMD_OFFSET_MOD_ADDR) = *(addr))
#define set_cmd_code(msg, code)  (*((uint8_t *)(msg) + MW_CMD_OFFSET_CMD_CODE) = (code))
#define set_rsp_flag(msg, flag)  (*((uint8_t *)(msg) + MW_CMD_OFFSET_RSP_FLAG) = (flag))
#define set_cmd_size(msg, size)  (*((uint8_t *)(msg) + MW_CMD_OFFSET_CMD_SIZE) = (size))
#define set_crc_data(msg, data)  (*(uint16_t *)((uint8_t *)(msg) + MW_CMD_OFFSET_CMD_DATA + get_cmd_size(msg)) = (data))

uint8_t mw_pa_powerswitch = 0;
uint8_t mw_lna_powerswitch = 0;
char mw_pa_sw_ver[64] = { 0 };

inline uint32_t swap32(uint8_t *buf)
{
	uint32_t d = buf[0];
	d <<= 8;
	d |= buf[1];
	d <<= 8;
	d |= buf[2];
	d <<= 8;
	d |= buf[3];
	return d;
}

inline uint32_t swap16(uint8_t *buf)
{
	uint32_t d = buf[0];
	d <<= 8;
	d |= buf[1];
	return d;
}

inline uint32_t swap_u16(uint16_t buf)
{
	return (buf & 0xFF) << 8 | (buf & 0xFF00) >> 8;
}

inline pa_ret_t mw_verify_start_end_flag(void *buf, int32_t len)
{
    if( len >= 2 )
    {
        uint8_t *ptr = (uint8_t *)buf;
        if( (ptr[0] == MW_MSG_HEAD_FLAG) && (ptr[len - 1] == MW_MSG_TAIL_FLAG) )
        {
            return pa_ret_ok;
        }
    }
    PALogError("wrong start-end flag");
    return pa_err_magic;
}

inline pa_ret_t mw_verify_mod_addr(void *buf, int32_t len, mod_addr_t *addr)
{
    if( len >= 3 )
    {
        mod_addr_t mod_addr = get_mod_addr(buf);
        if( (addr->func_code == mod_addr.func_code) && 
            (addr->addr_code == mod_addr.addr_code) )
        {
            return pa_ret_ok;
        }
    }
    PALogError("wrong module address");
    return pa_err_mod_addr;
}

inline pa_ret_t mw_verify_cmd_code(void *buf, int32_t len, uint8_t cmd_code)
{
    if( len >= 4 )
    {
        if( cmd_code == get_cmd_code(buf) )
        {
            return pa_ret_ok;
        }
    }
    PALogError("wrong command code");
    return pa_err_com_code;
}

inline pa_ret_t mw_verify_cmd_size(void *buf, int32_t len)
{
    if( len >= 6 )
    {
        uint8_t cmd_size = get_cmd_size(buf);
        if( cmd_size == len - 1 - 5 - 2 -1 )
        {
            return pa_ret_ok;
        }
    }
    return pa_err_cmd_size;
}

inline pa_ret_t mw_verify_crc(void *buf, int32_t len)
{
    if( len >= 8 )
    {
        uint16_t crc_data = get_crc_data(buf);
        uint8_t *ptr = (uint8_t *)buf;
        uint16_t crc_calc = crc16_ccitt(ptr + MW_CMD_OFFSET_MOD_ADDR, 5 + get_cmd_size(buf));
        if( crc_calc == crc_data )
        {
            return pa_ret_ok;
        }
        PALogError("crc value %X isn't match with %X", crc_data, crc_calc);
    }
    return pa_err_crc;
}

pa_ret_t mw_msg_check(uint8_t *buf, uint32_t len, mod_addr_t *lpa_addr, uint32_t cmd_code)
{
    pa_ret_t ret = pa_ret_ok;

    ret = mw_verify_start_end_flag(buf, len);
    if( pa_ret_ok != ret )
    {
        return ret;
    }

    ret = mw_verify_crc(buf, len);
    if( pa_ret_ok != ret )
    {
        return ret;
    }

    ret = mw_verify_mod_addr(buf, len, lpa_addr);
    if( pa_ret_ok != ret )
    {
        return ret;
    }

    ret = mw_verify_cmd_code(buf, len, cmd_code);
    if( pa_ret_ok != ret )
    {
        return ret;
    }
            
    if( MW_RSP_FLAG_OK != get_rsp_flag(buf) )
    {
        PALogError("check rsp flag failed, %d", get_rsp_flag(buf));
        return pa_err_rsp_flag;
    }
    return pa_ret_ok;
}

pa_ret_t mw_get_pa_status(int32_t com_fd, mod_addr_t *pa_addr, mw_pa_status *status)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[9] = {MW_MSG_HEAD_FLAG,MW_MOD_ADDR_FUNC_PA,0x00,MW_CMD_GET_MOD_STATUS,0x00,0x00,0xFF,0xFF,MW_MSG_TAIL_FLAG};
    
    set_mod_addr(com_cmd, pa_addr);

    *(uint16_t *)(com_cmd + MW_CMD_OFFSET_CMD_DATA) = crc16_ccitt(&com_cmd[1], 5);

    debug_dump_buffer(9, com_cmd);

    nret = write(com_fd, com_cmd, 9);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            if( ((rsp_buf[rsp_len-1] == 0x0) || (rsp_buf[rsp_len-1] == 0xFF)) && 
                 (rsp_buf[rsp_len-2] == MW_MSG_TAIL_FLAG) )
            {
                rsp_len -= 1;
            }
            debug_dump_buffer(rsp_len, rsp_buf);

            ret = mw_msg_check(rsp_buf, rsp_len, pa_addr, MW_CMD_GET_MOD_STATUS);
            if( pa_ret_ok != ret )
            {
                return ret;
            }

            mw_pa_status *read_status = (mw_pa_status *)(rsp_buf + MW_CMD_OFFSET_CMD_DATA);
            memcpy(status, read_status, sizeof(mw_pa_status));
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t mw_get_lna_status(int32_t com_fd, mod_addr_t *lna_addr, mw_lna_status *status)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[9] = {MW_MSG_HEAD_FLAG,MW_MOD_ADDR_FUNC_LNA,0x00,MW_CMD_GET_MOD_STATUS,0x00,0x00,0xFB,0xBB,MW_MSG_TAIL_FLAG};
    
    set_mod_addr(com_cmd, lna_addr);

    *(uint16_t *)(com_cmd + MW_CMD_OFFSET_CMD_DATA) = crc16_ccitt(&com_cmd[1], 5);

    debug_dump_buffer(9, com_cmd);

    nret = write(com_fd, com_cmd, 9);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            if( ((rsp_buf[rsp_len-1] == 0x0) || (rsp_buf[rsp_len-1] == 0xFF)) && 
                 (rsp_buf[rsp_len-2] == MW_MSG_TAIL_FLAG) )
            {
                rsp_len -= 1;
            }

            debug_dump_buffer(rsp_len, rsp_buf);

            ret = mw_msg_check(rsp_buf, rsp_len, lna_addr, MW_CMD_GET_MOD_STATUS);
            if( pa_ret_ok != ret )
            {
                return ret;
            }

            mw_lna_status *read_status = (mw_lna_status *)(rsp_buf + MW_CMD_OFFSET_CMD_DATA);
            memcpy(status, read_status, sizeof(mw_lna_status));
        }
        else
        {
            //printf("%s, nothing read \n", __func__);
            //return pa_uart_read_zero;
            memset(status, 0, sizeof(mw_lna_status));
            return ret;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t mw_get_pa_sw_ver(int32_t com_fd, mod_addr_t *pa_addr, char* sw_ver)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[9] = {MW_MSG_HEAD_FLAG,MW_MOD_ADDR_FUNC_PA,0x00,MW_CMD_GET_MOD_SWVER,0x00,0x00,0xFF,0xFF,MW_MSG_TAIL_FLAG};

    set_mod_addr(com_cmd, pa_addr);

    *(uint16_t *)(com_cmd + MW_CMD_OFFSET_CMD_DATA) = crc16_ccitt(&com_cmd[1], 5);

    debug_dump_buffer(9, com_cmd);

    nret = write(com_fd, com_cmd, 9);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            if( ((rsp_buf[rsp_len-1] == 0x0) || (rsp_buf[rsp_len-1] == 0xFF)) && 
                 (rsp_buf[rsp_len-2] == MW_MSG_TAIL_FLAG) )
            {
                rsp_len -= 1;
            }

            debug_dump_buffer(rsp_len, rsp_buf);
            ret = mw_msg_check(rsp_buf, rsp_len, pa_addr, MW_CMD_GET_MOD_SWVER);
            if( pa_ret_ok != ret )
            {
                return ret;
            }

            memcpy(mw_pa_sw_ver, rsp_buf + MW_CMD_OFFSET_CMD_DATA, get_cmd_size(rsp_buf));
            memcpy(sw_ver, mw_pa_sw_ver, get_cmd_size(rsp_buf));

            PALogNotice("pa software version: %s", mw_pa_sw_ver);
        }
        else
        {
            PALogWarning("nothing read \n");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

/*
 * 0x0X,0x00 is default address for module
 *      0x00 can be changed
 */
pa_ret_t mw_set_mod_addr(int32_t com_fd, mod_addr_t *mod_addr, uint8_t new_addr)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[11] = {MW_MSG_HEAD_FLAG,0x00,0x00,MW_CMD_SET_MOD_ADDR,0x00,0x02,0x00,0x00,0xFF,0xFF,MW_MSG_TAIL_FLAG};

    set_mod_addr(com_cmd, mod_addr);

    com_cmd[MW_CMD_OFFSET_CMD_DATA] = mod_addr->func_code;
    com_cmd[MW_CMD_OFFSET_CMD_DATA + 1] = 8 + new_addr;

    *(uint16_t *)(com_cmd + MW_CMD_OFFSET_CMD_DATA + 2) = crc16_ccitt(&com_cmd[1], 5 + 2);

    nret = write(com_fd, com_cmd, 11);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            if( ((rsp_buf[rsp_len-1] == 0x0) || (rsp_buf[rsp_len-1] == 0xFF)) && 
                 (rsp_buf[rsp_len-2] == MW_MSG_TAIL_FLAG) )
            {
                rsp_len -= 1;
            }

            debug_dump_buffer(rsp_len, rsp_buf);

            mod_addr_t addr = {mod_addr->func_code, (uint8_t)(8 + new_addr)};

            ret = mw_msg_check(rsp_buf, rsp_len, &addr, MW_CMD_SET_MOD_ADDR);
            if( pa_ret_ok != ret )
            {
                return ret;
            }
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t mw_set_mod_switch(int32_t com_fd, mod_addr_t *mod_addr, int8_t on_off)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[10] = {MW_MSG_HEAD_FLAG,0x00,0x00,MW_CMD_SET_SWITCH,0x00,0x01,0x00,0xFF,0xFF,MW_MSG_TAIL_FLAG};

    set_mod_addr(com_cmd, mod_addr);

    com_cmd[MW_CMD_OFFSET_CMD_DATA] = on_off;
    mw_pa_powerswitch = on_off;

    *(uint16_t *)(com_cmd + MW_CMD_OFFSET_CMD_DATA + 1) = crc16_ccitt(&com_cmd[1], 5 + 1);

    debug_dump_buffer(10, com_cmd);

    nret = write(com_fd, com_cmd, 10);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            if( ((rsp_buf[rsp_len-1] == 0x0) || (rsp_buf[rsp_len-1] == 0xFF)) && 
                 (rsp_buf[rsp_len-2] == MW_MSG_TAIL_FLAG) )
            {
                rsp_len -= 1;
            }

            debug_dump_buffer(rsp_len, rsp_buf);
            ret = mw_msg_check(rsp_buf, rsp_len, mod_addr, MW_CMD_SET_SWITCH);
            if( pa_ret_ok != ret )
            {
                return ret;
            }
        }
        else
        {
            PALogError("nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t mw_set_mod_switch_rf(int32_t com_fd, mod_addr_t *mod_addr, int8_t on_off)
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[10] = {MW_MSG_HEAD_FLAG,0x00,0x00,MW_CMD_SET_SWITCH_RF,0x00,0x01,0x00,0xFF,0xFF,MW_MSG_TAIL_FLAG};

    set_mod_addr(com_cmd, mod_addr);

    com_cmd[MW_CMD_OFFSET_CMD_DATA] = on_off;
    mw_pa_powerswitch = on_off;

    *(uint16_t *)(com_cmd + MW_CMD_OFFSET_CMD_DATA + 1) = crc16_ccitt(&com_cmd[1], 5 + 1);

    debug_dump_buffer(10, com_cmd);

    nret = write(com_fd, com_cmd, 10);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            if( ((rsp_buf[rsp_len-1] == 0x0) || (rsp_buf[rsp_len-1] == 0xFF)) && 
                 (rsp_buf[rsp_len-2] == MW_MSG_TAIL_FLAG) )
            {
                rsp_len -= 1;
            }

            debug_dump_buffer(rsp_len, rsp_buf);
            ret = mw_msg_check(rsp_buf, rsp_len, mod_addr, MW_CMD_SET_SWITCH);
            if( pa_ret_ok != ret )
            {
                return ret;
            }
        }
        else
        {
            PALogError("%s, nothing read");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

pa_ret_t mw_set_mod_att(int32_t com_fd, mod_addr_t *mod_addr, int8_t att, bool save_eeprom )
{
    pa_ret_t ret = pa_ret_ok;
    int32_t nret = 0;
    uint8_t com_cmd[10] = {MW_MSG_HEAD_FLAG,0x00,0x00,MW_CMD_SET_ATT,0x00,0x01,0x00,0xFF,0xFF,MW_MSG_TAIL_FLAG};

    set_mod_addr(com_cmd, mod_addr);
    if( save_eeprom )
    {
        com_cmd[MW_CMD_OFFSET_CMD_DATA] = att & 0x7F;
    }
    else
    {
        com_cmd[MW_CMD_OFFSET_CMD_DATA] = /*0x80 |*/ (att & 0x7F);
    }

    *(uint16_t *)(com_cmd + MW_CMD_OFFSET_CMD_DATA + 1) = crc16_ccitt(&com_cmd[1], 5 + 1);

    debug_dump_buffer(10, com_cmd);

    nret = write(com_fd, com_cmd, 10);
    if( nret > 0 )
    {
        int32_t rsp_len = 0;
        uint8_t rsp_buf[256] = {0};
        sleep(1);
        rsp_len = read(com_fd, rsp_buf, 256);
        if( rsp_len > 0 )
        {
            if( ((rsp_buf[rsp_len-1] == 0x0) || (rsp_buf[rsp_len-1] == 0xFF)) && 
                 (rsp_buf[rsp_len-2] == MW_MSG_TAIL_FLAG) )
            {
                rsp_len -= 1;
            }

            debug_dump_buffer(rsp_len, rsp_buf);
            ret = mw_msg_check(rsp_buf, rsp_len, mod_addr, MW_CMD_SET_ATT);
            if( pa_ret_ok != ret )
            {
                return ret;
            }
        }
        else
        {
            PALogError("nothing read \n");
            return pa_uart_read_zero;
        }
    }
    else
    {
        PALogError("error: can't write com port, ret=%d, errno=%d", nret, errno);
        return pa_err_uart_write;
    }
    return ret;
}

